#ifndef ISCANNER_HPP
#define ISCANNER_HPP

#include <zbar.h>
#include <iostream>
#include <opencv2/opencv.hpp>

#include <thread>
#include <mutex>


class IScanner : public zbar::ImageScanner
{
public:
    IScanner()
        : zbar::ImageScanner()
    {
        set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 0);
        set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_X_DENSITY, 1);
        set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_Y_DENSITY, 1);
        set_config(zbar::ZBAR_QRCODE, zbar::ZBAR_CFG_ENABLE, 1);
    }

    bool scan(const cv::Mat &mgs, std::vector<std::string> &data)
    {
        zbar::Image imageOne(mgs.cols, mgs.rows, "Y800", (uchar *)mgs.data, mgs.cols*mgs.rows);
        zbar::ImageScanner::scan(imageOne);

        data.clear();
        for(zbar::Image::SymbolIterator symbol = imageOne.symbol_begin();
            symbol != imageOne.symbol_end();
            ++symbol)
        {
            if(symbol->get_type_name().compare("QR-Code")==0)
            {
                data.push_back(symbol->get_data());
            }
        }
        return !data.empty();
    }



    static void test()
    {
        cv::VideoCapture c(0);

        IScanner ms;

        bool res=false;

        cv::Mat m;
        cv::Mat mg;

        std::vector<std::string> data;

        while(!res && c.read(m))
        {
            cv::cvtColor(m,mg,CV_BGR2GRAY);
            res=ms.scan(mg,data);
            cv::imshow("m",m);
            cv::waitKey(1);
        }

        std::cout<<data[0]<<"\n"<<std::flush;

        c.release();
        cv::waitKey(100000);
    }


    static void CalGrid(std::vector<cv::Point> &topLeft, cv::Size &areaSize, cv::Size imageSize, cv::Size overlapSize, cv::Size gridSize)
    {
        areaSize.width=(imageSize.width+(gridSize.width-1)*overlapSize.width)/gridSize.width;
        areaSize.height=(imageSize.height+(gridSize.height-1)*overlapSize.height)/gridSize.height;

        cv::Point tl(0,0);
        topLeft.clear();

        for(int i=0;i<gridSize.width;i++)
        {
            tl.y=0;
            for(int j=0;j<gridSize.height;j++)
            {
                topLeft.push_back(tl);

                tl.y+=areaSize.height-overlapSize.height;
            }
            tl.x+=areaSize.width-overlapSize.width;
        }
    }

    bool scan(const cv::Mat &mgs, std::vector<std::string> &data, std::vector<cv::Point> &vertex)
    {
        zbar::Image imageOne(mgs.cols, mgs.rows, "Y800", (uchar *)mgs.data, mgs.cols*mgs.rows);
        zbar::ImageScanner::scan(imageOne);

        data.clear();
        vertex.clear();

        for(zbar::Image::SymbolIterator symbol = imageOne.symbol_begin();
            symbol != imageOne.symbol_end();
            ++symbol)
        {
            if(symbol->get_type_name().compare("QR-Code")==0)
            {
                data.push_back(symbol->get_data());
                int n = symbol->get_location_size();
                for(int i=0;i<n;i++)
                {
                    vertex.push_back(cv::Point(symbol->get_location_x(i), symbol->get_location_y(i)));
                }
            }
        }
        return !data.empty();
    }

    void Draw(cv::Mat &canvas, std::vector<std::string> &data, std::vector<cv::Point> &vertex, cv::Scalar color=cv::Scalar(255,0,0))
    {
        for(size_t i=0;i<data.size();i++)
        {
            std::vector<cv::Point> v1(vertex.begin()+i*4, vertex.begin()+i*4+4);
            cv::polylines(canvas, v1, true, color, 1);
        }
    }

    static void test2(std::string path="/home/u/my_photo-1.jpg")
    {
        cv::Mat m=cv::imread(path, 0);

        m=m(cv::Rect(0,0,200,160)).clone();

        std::vector<std::string> data;

        std::vector<cv::Point> vertex;

        IScanner ms;

        int64_t t0=cv::getTickCount();

        for(int i=0;i<100;i++)
            bool res=ms.scan(m, data, vertex);

        int64_t t1=cv::getTickCount();

        std::cout<<"time="<<(t1-t0)/cv::getTickFrequency()*1000/100<<"ms\n"<<std::flush;

        cv::Mat canvas;
        cv::cvtColor(m,canvas, CV_GRAY2BGR);
        ms.Draw(canvas, data, vertex);
        cv::imshow(path,canvas);
        cv::waitKey(1e4);

        cv::destroyWindow(path);

    }

};



class QRCode
{
public:
    std::string data;
    cv::Point vertex[4];
public:
    QRCode()
    {

    }

    QRCode(const QRCode &src)
        : data(src.data)
    {
        vertex[0]=src.vertex[0];
        vertex[1]=src.vertex[1];
        vertex[2]=src.vertex[2];
        vertex[3]=src.vertex[3];
    }

    void operator=(const QRCode &src)
    {
        data=src.data;
        vertex[0]=src.vertex[0];
        vertex[1]=src.vertex[1];
        vertex[2]=src.vertex[2];
        vertex[3]=src.vertex[3];
    }

    cv::Point2f Center() const
    {
        cv::Vec3f d02=cv::Vec3f(vertex[0].x, vertex[0].y, 1).cross(cv::Vec3f(vertex[2].x, vertex[2].y, 1));
        cv::Vec3f d13=cv::Vec3f(vertex[1].x, vertex[1].y, 1).cross(cv::Vec3f(vertex[3].x, vertex[3].y, 1));
        cv::Vec3f c=d02.cross(d13);
        return cv::Point2f(c[0]/c[2],c[1]/c[2]);
    }

    void Draw(cv::Mat &canvas, cv::Scalar color=cv::Scalar(255,0,0)) const
    {
        std::vector<cv::Point> v1(vertex, vertex+4);
        cv::polylines(canvas, v1, true, color, 1);
    }

};



class QRScanner
{

    static void CalGrid(std::vector<cv::Point> &topLeft, cv::Size &areaSize, cv::Size imageSize, cv::Size overlapSize, cv::Size gridSize)
    {
        areaSize.width=(imageSize.width+(gridSize.width-1)*overlapSize.width)/gridSize.width;
        areaSize.height=(imageSize.height+(gridSize.height-1)*overlapSize.height)/gridSize.height;

        cv::Point tl(0,0);
        topLeft.clear();

        for(int i=0;i<gridSize.width;i++)
        {
            tl.y=0;
            for(int j=0;j<gridSize.height;j++)
            {
                topLeft.push_back(tl);

                tl.y+=areaSize.height-overlapSize.height;
            }
            tl.x+=areaSize.width-overlapSize.width;
        }
    }
public:
    std::vector<cv::Point> topLeft;
    cv::Size areaSize;
    zbar::ImageScanner* pScanner;

    QRScanner()
        : pScanner(NULL)
    {

    }

    void Release()
    {
        if(pScanner!=NULL)
        {
            delete []pScanner;
            pScanner=NULL;
        }
    }

    ~QRScanner()
    {
        Release();
    }

    void Initial(cv::Size imageSize, cv::Size overlapSize, cv::Size gridSize)
    {
        CalGrid(topLeft, areaSize, imageSize, overlapSize, gridSize);
        Release();
        pScanner=new zbar::ImageScanner[topLeft.size()];

        for(size_t i=0;i<topLeft.size();i++)
        {
            pScanner[i].set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 0);
            pScanner[i].set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_X_DENSITY, 1);
            pScanner[i].set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_Y_DENSITY, 1);
            pScanner[i].set_config(zbar::ZBAR_QRCODE, zbar::ZBAR_CFG_ENABLE, 1);

            std::cout<<topLeft[i]<<"\n";
        }
        std::cout<<"roi="<<areaSize<<"\n";
    }

    static bool CheckExist(const std::vector<QRCode> &result, const QRCode &res, float thres=10)
    {
        cv::Point2f ct=res.Center();
        for(auto &r1:result)
        {
            if(cv::norm(ct-r1.Center())<thres && r1.data.compare(res.data)==0)
                return true;
        }
        return false;
    }

    static void ScanROI(const cv::Mat &m, cv::Rect roi, zbar::ImageScanner* psr, std::mutex &mtx, std::vector<QRCode> &result)
    {
        cv::Mat mgs;
        m(roi).copyTo(mgs);
        zbar::Image imageOne(mgs.cols, mgs.rows, "Y800", (uchar *)mgs.data, mgs.cols*mgs.rows);
        psr->scan(imageOne);


        std::vector<QRCode> res;
        for(zbar::Image::SymbolIterator symbol = imageOne.symbol_begin();
            symbol != imageOne.symbol_end();
            ++symbol)
        {
            if(symbol->get_type_name().compare("QR-Code")==0)
            {
                QRCode code;
                code.data=symbol->get_data();
                int n = symbol->get_location_size();
                for(int i=0;i<n;i++)
                {
                    code.vertex[i]=cv::Point(symbol->get_location_x(i), symbol->get_location_y(i))+roi.tl();
                }
                res.push_back(code);
            }
        }

        mtx.lock();

        for(auto &res1:res)
        {
            if(!CheckExist(result, res1))
            {
                result.push_back(res1);
            }
        }

        mtx.unlock();
    }

    bool Scan(const cv::Mat &m, std::vector<QRCode> &result)
    {
        std::vector<std::thread> ths;

        std::mutex mtx;

        result.clear();

        for(size_t i=0;i<topLeft.size();i++)
        {
            ths.push_back(std::thread(ScanROI, std::ref(m), cv::Rect(topLeft[i], areaSize), pScanner+i, std::ref(mtx), std::ref(result)));
        }

        for(auto &th:ths)
            if(th.joinable())
                th.join();

        return !result.empty();
    }

    static void test2(std::string path="/home/u/my_photo-1.jpg")
    {
        cv::Mat m=cv::imread(path, 0);

        std::vector<QRCode> data;

        QRScanner ms;

        ms.Initial(m.size(), cv::Size(50,50), cv::Size(5,4));

        int64_t t0=cv::getTickCount();

        for(int i=0;i<100;i++)
            bool res=ms.Scan(m, data);

        int64_t t1=cv::getTickCount();

        std::cout<<"time="<<(t1-t0)/cv::getTickFrequency()*1000/100<<"ms\n"<<std::flush;

        cv::Mat canvas;
        cv::cvtColor(m,canvas, CV_GRAY2BGR);

        for(auto c1:data)
            c1.Draw(canvas);

        std::cout<<data.size()<<"\n"<<std::flush;

        cv::imshow(path,canvas);
        cv::waitKey(2e3);

        cv::destroyWindow(path);

    }



};

#endif // ISCANNER_HPP

